package com.pool.socket;

import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.*;

import org.json.JSONException;
import org.json.JSONObject;

import com.pool.socket.DataType.*;

public class SocketObserver extends Thread {
	// responsible for notify sender to send heart beat

	SocketServer server;
	long interval = 10 * 1000;
	byte[] buffer = new byte[1024];

	SocketObserver(SocketServer server, long timeout) {
		this.server = server;
		this.interval = timeout;
	}

	void sendHeartbeatToSocket(List<Socket> socketList) {
		HashMap<Socket, ByteBuffer> toBeSend = SocketServer.sender
				.getToBeSend();
		for (int i = 0; i < socketList.size(); i++) {
			Socket conn = socketList.get(i);
			ByteBuffer bb = prepareHeartBeat(conn);
			synchronized (toBeSend) {
				toBeSend.put(conn, bb);
				toBeSend.notify();
			}
		}
	}

	void closeEachSocket(List<Socket> socketList) {
		for (int i = 0; i < socketList.size(); i++) {
			Socket conn = socketList.get(i);
			closeSocket(conn);
		}
	}

	public void run() {
		while (!this.isInterrupted()) {
			try {
				HashMap<Socket, SocketDepict> pool = SocketServer
						.getSocketPool();
				List<Socket> timeoutSocket = new LinkedList<Socket>();
				List<Socket> toCloseSocket = new LinkedList<Socket>();
				synchronized (pool) {
					Set<Socket> socketSet = pool.keySet();
					System.out.println("SocketObserver socket numbers = "
							+ socketSet.size());
					for (Socket conn : socketSet) {
						if (pool.get(conn).number >= 4)
							toCloseSocket.add(conn);
						else
							timeoutSocket.add(conn);
					}
				}
				sendHeartbeatToSocket(timeoutSocket);
				closeEachSocket(toCloseSocket);
				sleep(interval);
			} catch (InterruptedException e) {
				System.out.println("Interrupted Exception happend!");
				e.printStackTrace();
			}
		}
	}

	private ByteBuffer prepareHeartBeat(Socket socket) {
		int localAddr = getLocalGatewayId(socket);
		ByteBuffer bb = ByteBuffer.wrap(new byte[18]);
		bb.order(MessageFormat.byteOrder);
		bb.put(MessageFormat.hexStringToByteArray("0x004345544300"));
		bb.put(MessageFormat.hexStringToByteArray("0x00010001"));
		bb.putInt(localAddr);
		bb.putShort((short) 2);
		bb.put(MessageFormat.hexStringToByte("0x68"));
		HashMap<Socket, SocketDepict> pool = SocketServer.getSocketPool();
		synchronized (pool) {
			SocketDepict description = pool.get(socket);
			byte beat = description.getHeartbeat();
			if ((beat % 2) == 0)
				beat = (byte) ((beat + 1) % 128);
			bb.put(beat); // odd number expect heart beat response
			description.setHeartbeat(beat);
			description.number++;
			System.out.println("send heat beat times = " + description.number);
			pool.put(socket, description);
		}
		return bb;
	}

	private int getLocalGatewayId(Socket socket) {
		int localAddr = 0;
		HashMap<Socket, SocketDepict> pool = SocketServer.getSocketPool();
		synchronized (SocketServer.GlobalToLocalId) {
			synchronized (pool) {
				SocketDepict depict = pool.get(socket);
				UUID uuid = depict.getGatewayUUID();
				System.out.println("uuid = " + uuid.toString());
				String json = SocketServer.GlobalToLocalId.get(uuid);
				System.out.println("json = " + json);
				try {
					JSONObject obj = new JSONObject(json);
					localAddr = obj.getInt("GatewayId");
					System.out.println("localAddr = " + localAddr);
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		}
		return localAddr;
	}

	static public void closeSocket(Socket socket) {
		HashMap<Socket, SocketDepict> pool = SocketServer.getSocketPool();
		synchronized (SocketServer.GlobalToLocalId) {
			synchronized (pool) {
				SocketDepict depict = pool.get(socket);
				UUID uuid = depict.getGatewayUUID();
				// String jsonStr = SocketServer.GlobalToLocalId.get(uuid);
				pool.remove(socket);
				// SocketServer.GlobalToLocalId.remove(uuid);
				SocketServer.GlobalIdToSocket.remove(uuid);
				// SocketServer.LocalIdToGlobal.remove(jsonStr);
			}
		}
		try {
			socket.close(); // close client socket
			System.out.println(Thread.currentThread().getName()
					+ " close socket !");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
